#pragma once

#include "util.hpp"
#include "matcher.hpp"
#include "online.hpp"
#include "room.hpp"
#include "session.hpp"
#include"log.hpp"

#define WWWROOT "./wwwroot"

class gobang_server
{

private:
    user_table _ut;
    online_manager _om;
    room_manager _rm;
    session_manager _sm;
    matcher _mm;
    websocket_server _wssrv;
    std::string _web_root; // 静态资源根目录

private:
    // 四个业务回调函数，对于http请求，我们需要根据不同的请求，进行不同的响应
    // 我们约定的有相应的Json

    void file_handler(websocket_server::connection_ptr &conn)
    {
        // 静态资源

        // 获取请求uri路径，组合出实际文件目录

        // 如果请求的是一个目录增加后缀
        websocketpp::http::parser::request req = conn->get_request();

        std::string uri = req.get_uri(); // 得到申请获取文件的名称

        std::string realpath = _web_root + uri;

        if (realpath.back() == '/')
        {
            realpath += "login.html";
        }

        // 读取文件内容

        std::string body;
        bool ret = ns_util::file_util::read(realpath, &body);

        // 如果文件不存在，读取文件内容失败，返回404;
        if (ret == false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset='UTF-8'/>";
            body += "404";
            body += "</br>";
            body += "Not Found";
            body += "</head>";
            body += "</html>";
            // 设置文件的内容
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::not_found);

            return;
        }

        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
    }

    void http_resp(websocket_server::connection_ptr &conn, bool result, websocketpp::http::status_code::value code, const std::string &reason)
    {
        Json::Value resp_json;
        resp_json["reason"] = reason;
        resp_json["result"] = result;

        std::string resp_body;

        ns_util::json_util::json_serialize(resp_json, &resp_body);

        conn->set_status(code);
        conn->set_body(resp_body);
        conn->append_header("Content-Type", "application/json");
        return;
    }

    void reg(websocket_server::connection_ptr &conn)
    {
        // 注册
        // 获取请求正文
        websocketpp::http::parser::request req = conn->get_request();
        // 对正文进行反序列化，得到用户名和用户密码

        std::string val = req.get_body();

        Json::Value login_info;

        bool ret;
        ret = ns_util::json_util::json_unserialize(&login_info, val);
        if (ret == false)
        {
            LOG_ERR("反序列化失败");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "反序列化失败");
        }

        // 进行数据库的用户操作
        // 在进行用户注册操作前，对用户名和用户密码进行检测
        if (login_info["username"].isNull() || login_info["password"].isNull())
        {
            LOG_INF("用户少输入名称或密码");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入名称/密码");
        }

        ret = _ut.insert(login_info);

        if (ret == false)
        {
            LOG_ERR("数据库添加数据失败");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "数据库添加数据失败");
        }

        return http_resp(conn, true, websocketpp::http::status_code::ok, "注册用户成功");

        // 如果成功了返回200
    }

    void login(websocket_server::connection_ptr &conn)
    {
        // 登录
        // 获取请求数据
        websocketpp::http::parser::request req = conn->get_request();

        std::string login_info = req.get_body();

        Json::Value val;

        bool ret;
        ret = ns_util::json_util::json_unserialize(&val, login_info);

        if (ret == false)
        {
            LOG_ERR("反序列化失败");

            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "反序列化失败");
        }

        // 进行数据库操作
        // 验证数据

        std::cout << login_info << std::endl;

        if (val["username"].isNull() || val["password"].isNull())
        {
            LOG_ERR("用户少输入了名称或密码");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "请输入用户名/用户密码");
        }

        ret = _ut.login(val);

        if (ret == false)
        {
            LOG_ERR("用户名/密码错误");
            return http_resp(conn, false, websocketpp::http::status_code::bad_request, "用户名/用户密码错误");
        }

        // 如果验证成功，给用户创建session
        uint64_t uid = val["id"].asInt64();
        session_ptr ssp = _sm.create_session(uid, LOGIN);

        if (ssp.get() == nullptr)
        {
            LOG_INF("创建会话失败");
            return http_resp(conn, false, websocketpp::http::status_code::internal_server_error, "创建会话失败");
        }

        // 为会话创建超时时间，当网页关闭时，会话超时会被自动释放
        _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);

        // 设置响应头部set_cokie,将session通过cookie返回

        std::string cookie_ssid = "SSID=" + std::to_string(ssp->ssid());

        conn->append_header("Set-Cookie", cookie_ssid);
        return http_resp(conn, true, websocketpp::http::status_code::ok, "登录成功");
    }

    //--------------------------------------------------------------------------------------------------------------------------------//

    bool get_cookie_val(std::string cookie_str, const std::string &key, std::string &val)
    {
        // cookie的格式： SSID=XXXX; path=XXX;
        std::string sep = "; ";
        std::vector<std::string> cookie_arr;
        ns_util::string_util::split(cookie_str, sep, cookie_arr);

        for (auto str : cookie_arr)
        {
            // 对单个的cookie字符串以=进行分割
            std::vector<std::string> tmp_arr;
            ns_util::string_util::split(str, "=", tmp_arr);

            if (tmp_arr.size() != 2)
            {
                continue;
            }

            if (tmp_arr[0] == key)
            {
                val = tmp_arr[1];
                return true;
            }
        }
        return false;
    }

    session_ptr get_session_by_cookie(websocket_server::connection_ptr &conn)
    {
        // 获取信息
        Json::Value err_resp;
        // 获取请求信息中的cookie,从cookie中获取ssid

        std::string cookie_str = conn->get_request_header("Cookie");
        if (cookie_str.empty())
        {
            // 如果没有cookie让用户重登录
            LOG_INF("没有cookie");
            http_resp(conn, false, websocketpp::http::status_code::bad_request, "缺少cookie");

            return session_ptr();
        }

        // 分解cookie,从cookie中获取ssid
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str, "SSID", ssid_str);

        if (ret == false)
        {
            http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到ssid信息");
            return session_ptr();
        }

        // 在session管理中查找对应的会话信息
        session_ptr ssp = _sm.get_session_by_ssid(std::stoi(ssid_str));
        if (ssp.get() == nullptr)
        {
            http_resp(conn, true, websocketpp::http::status_code::bad_request, "登录过期，请重新登录");

            return session_ptr();
        }

        return ssp;
    }

    void info(websocket_server::connection_ptr &conn)
    {
        // 验证登录信息

        session_ptr ssp = get_session_by_cookie(conn);

        if (ssp.get() == nullptr)
        {
            return;
        }

        // 从数据库中取出用户信息，进行序列化后发送给客户端

        bool ret;
        uint64_t uid = ssp->get_uid();

        Json::Value user_info;
        ret = _ut.select_by_id(uid, user_info);

        if (ret == false)
        {
            return http_resp(conn, true, websocketpp::http::status_code::bad_request, "找不到用户信息，请重新登录");
        }

        std::string body;
        ns_util::json_util::json_serialize(user_info, &body);

        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
        conn->append_header("Content-Type", "application/json");

        _sm.set_session_expire_time(ssp->get_uid(), SESSION_TIMEOUT);
    }

    void http_callback(websocketpp::connection_hdl hdl)
    {

        websocket_server::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);

        websocketpp::http::parser::request req = conn->get_request();

        std::string method = req.get_method(); // 请求方法
        std::string uri = req.get_uri();       // 获取文件

        if (method == "POST" && uri == "/reg")
        {
            return reg(conn);
        }
        else if (method == "POST" && uri == "/login")
        {
            return login(conn);
        }
        else if (method == "GET" && uri == "/info")
        {
            info(conn);
        }
        else
        {
            return file_handler(conn);
        }
    }

    void ws_resp(websocket_server::connection_ptr &conn, std::string reason, std::string optype, bool result)
    {
        Json::Value err_resp;
        err_resp["reason"] = reason;
        err_resp["result"] = result;
        err_resp["optype"] = optype;

        std::string body;
        ns_util::json_util::json_serialize(err_resp, &body);

        conn->send(body);
    }

    void wsopen_game_hall(websocket_server::connection_ptr conn)
    {
        // 登录验证 获取信息

        session_ptr ssp = get_session_by_cookie(conn);

        if (ssp.get() == nullptr)
        {
            return;
        }

        // 判断当前客户端是否是重复登录

        if (_om.is_in_game_hall(ssp->get_uid()) || _om.is_in_game_room(ssp->get_uid()))
        {
            return ws_resp(conn, "玩家重复登录", "hall_ready", false);
        }
        // 将当前客户端以及连接加入游戏大厅管理
        _om.enter_game_hall(ssp->get_uid(), conn);

        // 给客户端响应，表示游戏大厅连接建立成功
        ws_resp(conn, "建立连接成功", "hall_ready", true);

        // 将session设置为永久存在
        _sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);

    }

    void wsopen_game_room(websocket_server::connection_ptr conn)
    {
        // 获取当前客户端的session

        session_ptr ssp = get_session_by_cookie(conn);
        if (ssp.get() == nullptr)
        {
            return;
        }

        // 判断当前玩家是否已经在其它游戏房间，

        std::cout<<"当前game_room用户uid: "<<ssp->get_uid()<<std::endl;

        if (_om.is_in_game_hall(ssp->get_uid()) || _om.is_in_game_room(ssp->get_uid()))
        {
            return ws_resp(conn, "玩家重复登录", "room_ready", false);
        }

        // 判断当前用户玩家是否已经创建好了房间
        room_ptr rp = _rm.get_room_by_uid(ssp->get_uid());

        if (rp.get() == nullptr)
        {
            return ws_resp(conn, "没有找到玩家房间信息", "room_ready", false);
        }

        // 将当前用户添加到在线用户管理的游戏房间中
        _om.enter_game_room(ssp->get_uid(), conn);

        // 将session重新设置为永久存在
        _sm.set_session_expire_time(ssp->ssid(), SESSION_FOREVER);

        // 回复房间准备完毕
        Json::Value ve;

        std::cout << "房间准备完毕" << std::endl;

        ve["optype"] = "room_ready";
        ve["result"] = true;
        ve["room_id"] = (Json::UInt64)rp->id();
        ve["uid"] = (Json::UInt64)ssp->get_uid();
        ve["white_id"] = (Json::UInt64)rp->get_white_user();
        ve["black_id"] = (Json::UInt64)rp->get_black_user();

        std::string body;
        bool ret = ns_util::json_util::json_serialize(ve, &body);

        if (ret == false)
        {
            return ws_resp(conn, "序列化失败", "room_ready", false);
        }

        conn->send(body);

        return;
    }

    void wsopen_callback(websocketpp::connection_hdl hdl)
    {
        // websocket长连接建立成功之后的处理函数
        websocket_server::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);

        websocketpp::http::parser::request req = conn->get_request();

        std::string uri = req.get_uri();

        std::cout << "open uri: " << uri << std::endl;

        if (uri == "/hall")
        {
            // 建立了游戏大厅的长连接
            return wsopen_game_hall(conn);
        }
        else if (uri == "/room")
        {
            // 这里没有写
            //  建立了游戏房间的长连接
            return wsopen_game_room(conn);
        }

    }

    void wsclose_game_hall(websocket_server::connection_ptr conn)
    {
         // 登录验证 获取信息
        // 获取请求信息中的cookie,从cookie中获取ssid

        session_ptr ssp = get_session_by_cookie(conn);

        if (ssp.get() == nullptr)
        {
            return;
        }

        // 将玩家从游戏大厅中移除
        _om.exit_game_hall(ssp->get_uid());

        // 将session恢复生命周期的管理，设置定时销毁

        _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);
    }

    void wsclose_game_room(websocket_server::connection_ptr conn)
    {
         // 登录验证 获取信息
        // 获取请求信息中的cookie,从cookie中获取ssid

        session_ptr ssp = get_session_by_cookie(conn);

        if (ssp.get() == nullptr)
        {
            return;
        }

        // 将玩家从游戏大厅中移除
        _om.exit_game_room(ssp->get_uid());

        // 将session恢复生命周期的管理，设置定时销毁

        _sm.set_session_expire_time(ssp->ssid(), SESSION_TIMEOUT);

        //将玩家从游戏房间中移除
        _rm.remove_room_user(ssp->get_uid());


    }

    void wsclose_callback(websocketpp::connection_hdl hdl)
    {

        websocket_server::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);
        // 登录验证 获取信息
        // 获取请求信息中的cookie,从cookie中获取ssid

        websocketpp::http::parser::request req=conn->get_request();
        std::string uri=req.get_uri();

        if(uri=="/hall")
        {
            std::cout<<"大厅连接关闭"<<std::endl;
            return wsclose_game_hall(conn);
        }
        else if(uri=="/room")
        {
            std::cout<<"房间连接关闭"<<std::endl;
            return wsclose_game_room(conn);
        }
    }

    void wmsmsg_game_hall(websocket_server::connection_ptr conn, websocket_server::message_ptr msg)
    {

        // 身份验证，当前客户端是哪个玩家

        session_ptr ssp = get_session_by_cookie(conn);
        if (ssp.get() == nullptr)
        {
            return;
        }

        // 获取请求信息

        std::string req_body = msg->get_payload();

        std::cout << "请求信息：" << req_body << std::endl;

        Json::Value req_json;

        bool ret = ns_util::json_util::json_unserialize(&req_json, req_body);

        if (ret == false)
        {
            ws_resp(conn, "请求信息失败", "game_unready", false);
            return;
        }

        // 请求处理
        // 这里我们直接比较，防止空数据
        //!!!!!!!!!!!!!!!
        // 这里的是req_json["optype"].asString();
        if (req_json["optype"].asCString() == nullptr)
        {
            // 无效字段
            ws_resp(conn, "无效的请求字段", "unknown", false);
        }

        std::string op = req_json["optype"].asCString();
        std::cout << op << std::endl;

        // 这里不能用req_json["optype"].asCString() == "match_start",这样比较是比较两个指针是否相同
        if (op == "match_start")
        {
            // 加入匹配队列
            std::cout << "加入匹配队列" << std::endl;
            _mm.add(ssp->get_uid());
            return ws_resp(conn, "进入匹配", "match_start", true);
        }
        else if (op == "match_stop")
        {
            // 将玩家从匹配队列中移除
            _mm.del(ssp->get_uid());

            return ws_resp(conn, "退出匹配队列", "match_stop", true);
        }

        // 无效字段
        ws_resp(conn, "无效的请求字段", "unknown", false);
    }

    void wsmsg_game_room(websocket_server::connection_ptr conn, websocket_server::message_ptr msg)
    {
        Json::Value resp_json;

        // 获取用户端session，识别客户端身份
        session_ptr ssp=get_session_by_cookie(conn);

        if(ssp.get()==nullptr)
        {
            std::cout<<"wsmsg_game_room: 没有找到房间会话信息"<<std::endl;
            return;
        }

        //获取客户端房间信息
        room_ptr rp=_rm.get_room_by_uid(ssp->get_uid());
        if(rp.get()==nullptr)
        {
            return ws_resp(conn,"wsmsg_game_room:没有找到房间信息","unknown",false);
        }

        Json::Value req_json;

        std::string req_body=msg->get_payload();

        bool ret=ns_util::json_util::json_unserialize(&req_json,req_body);

        if(ret==false)
        {
            return ws_resp(conn,"wsmsg_game_room:反序列化失败","unknown",false);
        }
        
        std::cout<<"房间收到消息请求，开始处理....."<<std::endl;

        rp->handle_request(req_json);

        return;

    }

    void wmsmsg_callback(websocketpp::connection_hdl hdl, websocket_server::message_ptr msg)
    {

        websocket_server::connection_ptr conn = _wssrv.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();

        std::string uri = req.get_uri();

        std::cout << "uri: " << uri << std::endl;

        // 游戏大厅的消息处理

        if (uri == "/hall")
        {
            return wmsmsg_game_hall(conn, msg);
        }
        else if (uri == "/room")
        {
            return wsmsg_game_room(conn,msg);
        }

        // 游戏房间消息处理
    }

public:
    // 成员初始化，服务器回调函数的设置
    gobang_server(const std::string &host, const std::string &user, const std::string &password, const std::string &name, uint16_t port = 3306, const std::string wwwroot = WWWROOT) : _ut(host, user, password, name, port), _sm(&_wssrv), _rm(&_ut, &_om), _mm(&_rm, &_ut, &_om), _web_root(wwwroot)
    {

        // 设置回调函数
        _wssrv.set_access_channels(websocketpp::log::alevel::none);
        _wssrv.init_asio();
        _wssrv.set_reuse_addr(true);
        _wssrv.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
        _wssrv.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));

        _wssrv.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));

        _wssrv.set_message_handler(std::bind(&gobang_server::wmsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));
    }

    // 启动服务器
    void start(int port)
    {
        // 服务器监听
        _wssrv.listen(port);

        // 服务器接受
        _wssrv.start_accept();

        // 服务器启动
        _wssrv.run();
    }
};